<template>
    <view class="uni-file-picker">
        <view v-if="title" class="uni-file-picker__header">
            <text class="file-title">{{ title }}</text>
            <text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>
        </view>
        <upload-image v-if="fileMediatype === 'image' && showType === 'grid'" :readonly="readonly"
                      :image-styles="imageStyles" :files-list="filesList" :limit="limitLength"
                      :disablePreview="disablePreview"
                      :delIcon="delIcon" @uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
            <slot>
                <view class="is-add">
                    <view class="icon-add"></view>
                    <view class="icon-add rotate"></view>
                </view>
            </slot>
        </upload-image>
        <upload-file v-if="fileMediatype !== 'image' || showType !== 'grid'" :readonly="readonly"
                     :list-styles="listStyles" :files-list="filesList" :showType="showType" :delIcon="delIcon"
                     @uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
            <slot>
                <button type="primary" size="mini">选择文件</button>
            </slot>
        </upload-file>
    </view>
</template>

<script>
    import {
        chooseAndUploadFile,
        uploadCloudFiles
    } from './choose-and-upload-file.js'
    import {
        get_file_ext,
        get_extname,
        get_files_and_is_max,
        get_file_info,
        get_file_data
    } from './utils.js'
    import uploadImage from './upload-image.vue'
    import uploadFile from './upload-file.vue'

    let fileInput = null
    /**
     * FilePicker 文件选择上传
     * @description 文件选择上传组件，可以选择图片、视频等任意文件并上传到当前绑定的服务空间
     * @tutorial https://ext.dcloud.net.cn/plugin?id=4079
     * @property {Object|Array}    value    组件数据，通常用来回显 ,类型由return-type属性决定
     * @property {Boolean}    disabled = [true|false]    组件禁用
     *    @value true    禁用
     *    @value false    取消禁用
     * @property {Boolean}    readonly = [true|false]    组件只读，不可选择，不显示进度，不显示删除按钮
     *    @value true    只读
     *    @value false    取消只读
     * @property {String}    return-type = [array|object]    限制 value 格式，当为 object 时 ，组件只能单选，且会覆盖
     *    @value array    规定 value 属性的类型为数组
     *    @value object    规定 value 属性的类型为对象
     * @property {Boolean}    disable-preview = [true|false]    禁用图片预览，仅 mode:grid 时生效
     *    @value true    禁用图片预览
     *    @value false    取消禁用图片预览
     * @property {Boolean}    del-icon = [true|false]    是否显示删除按钮
     *    @value true    显示删除按钮
     *    @value false    不显示删除按钮
     * @property {Boolean}    auto-upload = [true|false]    是否自动上传，值为true则只触发@select,可自行上传
     *    @value true    自动上传
     *    @value false    取消自动上传
     * @property {Number|String}    limit    最大选择个数 ，h5 会自动忽略多选的部分
     * @property {String}    title    组件标题，右侧显示上传计数
     * @property {String}    mode = [list|grid]    选择文件后的文件列表样式
     *    @value list    列表显示
     *    @value grid    宫格显示
     * @property {String}    file-mediatype = [image|video|all]    选择文件类型
     *    @value image    只选择图片
     *    @value video    只选择视频
     *    @value all        选择所有文件
     * @property {Array}    file-extname    选择文件后缀，根据 file-mediatype 属性而不同
     * @property {Object}    list-style    mode:list 时的样式
     * @property {Object}    image-styles    选择文件后缀，根据 file-mediatype 属性而不同
     * @event {Function} select    选择文件后触发
     * @event {Function} progress 文件上传时触发
     * @event {Function} success    上传成功触发
     * @event {Function} fail        上传失败触发
     * @event {Function} delete    文件从列表移除时触发
     */
    export default {
        name: 'uniFilePicker',
        components: {
            uploadImage,
            uploadFile
        },
        emits: ['select', 'success', 'fail', 'progress', 'delete', 'update:modelValue', 'input'],
        props: {
            // #ifdef VUE3
            modelValue: {
                type: [Array, Object],
                default() {
                    return []
                }
            },
            // #endif

            // #ifndef VUE3
            value: {
                type: [Array, Object],
                default() {
                    return []
                }
            },
            // #endif

            disabled: {
                type: Boolean,
                default: false
            },
            disablePreview: {
                type: Boolean,
                default: false
            },
            delIcon: {
                type: Boolean,
                default: true
            },
            // 自动上传
            autoUpload: {
                type: Boolean,
                default: true
            },
            // 最大选择个数 ，h5只能限制单选或是多选
            limit: {
                type: [Number, String],
                default: 9
            },
            // 列表样式 grid | list | list-card
            mode: {
                type: String,
                default: 'grid'
            },
            // 选择文件类型  image/video/all
            fileMediatype: {
                type: String,
                default: 'image'
            },
            // 文件类型筛选
            fileExtname: {
                type: [Array, String],
                default() {
                    return []
                }
            },
            title: {
                type: String,
                default: ''
            },
            listStyles: {
                type: Object,
                default() {
                    return {
                        // 是否显示边框
                        border: true,
                        // 是否显示分隔线
                        dividline: true,
                        // 线条样式
                        borderStyle: {}
                    }
                }
            },
            imageStyles: {
                type: Object,
                default() {
                    return {
                        width: 'auto',
                        height: 'auto'
                    }
                }
            },
            readonly: {
                type: Boolean,
                default: false
            },
            returnType: {
                type: String,
                default: 'array'
            },
            sizeType: {
                type: Array,
                default() {
                    return ['original', 'compressed']
                }
            }
        },
        data() {
            return {
                files: [],
                localValue: []
            }
        },
        watch: {
            // #ifndef VUE3
            value: {
                handler(newVal, oldVal) {
                    this.setValue(newVal, oldVal)
                },
                immediate: true
            },
            // #endif
            // #ifdef VUE3
            modelValue: {
                handler(newVal, oldVal) {
                    this.setValue(newVal, oldVal)
                },
                immediate: true
            },
            // #endif
        },
        computed: {
            filesList() {
                let files = []
                this.files.forEach(v => {
                    files.push(v)
                })
                return files
            },
            showType() {
                if (this.fileMediatype === 'image') {
                    return this.mode
                }
                return 'list'
            },
            limitLength() {
                if (this.returnType === 'object') {
                    return 1
                }
                if (!this.limit) {
                    return 1
                }
                if (this.limit >= 9) {
                    return 9
                }
                return this.limit
            }
        },
        created() {
            // TODO 兼容不开通服务空间的情况
            if (!(uniCloud.config && uniCloud.config.provider)) {
                this.noSpace = true
                uniCloud.chooseAndUploadFile = chooseAndUploadFile
            }
            this.form = this.getForm('uniForms')
            this.formItem = this.getForm('uniFormsItem')
            if (this.form && this.formItem) {
                if (this.formItem.name) {
                    this.rename = this.formItem.name
                    this.form.inputChildrens.push(this)
                }
            }
        },
        methods: {
            /**
             * 公开用户使用，清空文件
             * @param {Object} index
             */
            clearFiles(index) {
                if (index !== 0 && !index) {
                    this.files = []
                    this.$nextTick(() => {
                        this.setEmit()
                    })
                } else {
                    this.files.splice(index, 1)
                }
                this.$nextTick(() => {
                    this.setEmit()
                })
            },
            /**
             * 公开用户使用，继续上传
             */
            upload() {
                let files = []
                this.files.forEach((v, index) => {
                    if (v.status === 'ready' || v.status === 'error') {
                        files.push(Object.assign({}, v))
                    }
                })
                this.uploadFiles(files)
            },
            async setValue(newVal, oldVal) {
                const newData = async (v) => {
                    const reg = /cloud:\/\/([\w.]+\/?)\S*/
                    let url = ''
                    if (v.fileID) {
                        url = v.fileID
                    } else {
                        url = v.url
                    }
                    if (reg.test(url)) {
                        v.fileID = url
                        v.url = await this.getTempFileURL(url)
                    }
                    if (v.url) v.path = v.url
                    return v
                }
                if (this.returnType === 'object') {
                    if (newVal) {
                        await newData(newVal)
                    } else {
                        newVal = {}
                    }
                } else {
                    if (!newVal) newVal = []
                    for (let i = 0; i < newVal.length; i++) {
                        let v = newVal[i]
                        await newData(v)
                    }
                }
                this.localValue = newVal
                if (this.form && this.formItem && !this.is_reset) {
                    this.is_reset = false
                    this.formItem.setValue(this.localValue)
                }
                let filesData = Object.keys(newVal).length > 0 ? newVal : [];
                this.files = [].concat(filesData)
            },

            /**
             * 选择文件
             */
            choose() {

                if (this.disabled) return
                if (this.files.length >= Number(this.limitLength) && this.showType !== 'grid' && this.returnType ===
                    'array') {
                    uni.showToast({
                        title: `您最多选择 ${this.limitLength} 个文件`,
                        icon: 'none'
                    })
                    return
                }
                this.chooseFiles()
            },

            /**
             * 选择文件并上传
             */
            chooseFiles() {
                const _extname = get_extname(this.fileExtname)
                // 获取后缀
                uniCloud
                    .chooseAndUploadFile({
                        type: this.fileMediatype,
                        compressed: false,
                        sizeType: this.sizeType,
                        // TODO 如果为空，video 有问题
                        extension: _extname.length > 0 ? _extname : undefined,
                        count: this.limitLength - this.files.length, //默认9
                        onChooseFile: this.chooseFileCallback,
                        onUploadProgress: progressEvent => {
                            this.setProgress(progressEvent, progressEvent.index)
                        }
                    })
                    .then(result => {
                        this.setSuccessAndError(result.tempFiles)
                    })
                    .catch(err => {
                        console.log('选择失败', err)
                    })
            },

            /**
             * 选择文件回调
             * @param {Object} res
             */
            async chooseFileCallback(res) {
                const _extname = get_extname(this.fileExtname)
                const is_one = (Number(this.limitLength) === 1 &&
                    this.disablePreview &&
                    !this.disabled) ||
                    this.returnType === 'object'
                // 如果这有一个文件 ，需要清空本地缓存数据
                if (is_one) {
                    this.files = []
                }

                let {
                    filePaths,
                    files
                } = get_files_and_is_max(res, _extname)
                if (!(_extname && _extname.length > 0)) {
                    filePaths = res.tempFilePaths
                    files = res.tempFiles
                }

                let currentData = []
                for (let i = 0; i < files.length; i++) {
                    if (this.limitLength - this.files.length <= 0) break
                    files[i].uuid = Date.now()
                    let filedata = await get_file_data(files[i], this.fileMediatype)
                    filedata.progress = 0
                    filedata.status = 'ready'
                    this.files.push(filedata)
                    currentData.push({
                        ...filedata,
                        file: files[i]
                    })
                }
                this.$emit('select', {
                    tempFiles: currentData,
                    tempFilePaths: filePaths
                })
                res.tempFiles = files
                // 停止自动上传
                if (!this.autoUpload || this.noSpace) {
                    res.tempFiles = []
                }
            },

            /**
             * 批传
             * @param {Object} e
             */
            uploadFiles(files) {
                files = [].concat(files)
                uploadCloudFiles.call(this, files, 5, res => {
                    this.setProgress(res, res.index, true)
                })
                    .then(result => {
                        this.setSuccessAndError(result)
                    })
                    .catch(err => {
                        console.log(err)
                    })
            },

            /**
             * 成功或失败
             */
            async setSuccessAndError(res, fn) {
                let successData = []
                let errorData = []
                let tempFilePath = []
                let errorTempFilePath = []
                for (let i = 0; i < res.length; i++) {
                    const item = res[i]
                    const index = item.uuid ? this.files.findIndex(p => p.uuid === item.uuid) : item.index

                    if (index === -1 || !this.files) break
                    if (item.errMsg === 'request:fail') {
                        this.files[index].url = item.path
                        this.files[index].status = 'error'
                        this.files[index].errMsg = item.errMsg
                        // this.files[index].progress = -1
                        errorData.push(this.files[index])
                        errorTempFilePath.push(this.files[index].url)
                    } else {
                        this.files[index].errMsg = ''
                        this.files[index].fileID = item.url
                        const reg = /cloud:\/\/([\w.]+\/?)\S*/
                        if (reg.test(item.url)) {
                            this.files[index].url = await this.getTempFileURL(item.url)
                        } else {
                            this.files[index].url = item.url
                        }

                        this.files[index].status = 'success'
                        this.files[index].progress += 1
                        successData.push(this.files[index])
                        tempFilePath.push(this.files[index].fileID)
                    }
                }

                if (successData.length > 0) {
                    this.setEmit()
                    // 状态改变返回
                    this.$emit('success', {
                        tempFiles: this.backObject(successData),
                        tempFilePaths: tempFilePath
                    })
                }

                if (errorData.length > 0) {
                    this.$emit('fail', {
                        tempFiles: this.backObject(errorData),
                        tempFilePaths: errorTempFilePath
                    })
                }
            },

            /**
             * 获取进度
             * @param {Object} progressEvent
             * @param {Object} index
             * @param {Object} type
             */
            setProgress(progressEvent, index, type) {
                const fileLenth = this.files.length
                const percentNum = (index / fileLenth) * 100
                const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
                let idx = index
                if (!type) {
                    idx = this.files.findIndex(p => p.uuid === progressEvent.tempFile.uuid)
                }
                if (idx === -1 || !this.files[idx]) return
                // fix by mehaotian 100 就会消失，-1 是为了让进度条消失
                this.files[idx].progress = percentCompleted - 1
                // 上传中
                this.$emit('progress', {
                    index: idx,
                    progress: parseInt(percentCompleted),
                    tempFile: this.files[idx]
                })
            },

            /**
             * 删除文件
             * @param {Object} index
             */
            delFile(index) {
                this.$emit('delete', {
                    tempFile: this.files[index],
                    tempFilePath: this.files[index].url
                })
                this.files.splice(index, 1)
                this.$nextTick(() => {
                    this.setEmit()
                })
            },

            /**
             * 获取文件名和后缀
             * @param {Object} name
             */
            getFileExt(name) {
                const last_len = name.lastIndexOf('.')
                const len = name.length
                return {
                    name: name.substring(0, last_len),
                    ext: name.substring(last_len + 1, len)
                }
            },

            /**
             * 处理返回事件
             */
            setEmit() {
                let data = []
                if (this.returnType === 'object') {
                    data = this.backObject(this.files)[0]
                    this.localValue = data ? data : null
                } else {
                    data = this.backObject(this.files)
                    if (!this.localValue) {
                        this.localValue = []
                    }
                    this.localValue = [...data]
                }
                // #ifdef VUE3
                this.$emit('update:modelValue', this.localValue)
                // #endif
                // #ifndef VUE3
                this.$emit('input', this.localValue)
                // #endif
            },

            /**
             * 处理返回参数
             * @param {Object} files
             */
            backObject(files) {
                let newFilesData = []
                files.forEach(v => {
                    newFilesData.push({
                        extname: v.extname,
                        fileType: v.fileType,
                        image: v.image,
                        name: v.name,
                        path: v.path,
                        size: v.size,
                        fileID: v.fileID,
                        url: v.url
                    })
                })
                return newFilesData
            },
            async getTempFileURL(fileList) {
                fileList = {
                    fileList: [].concat(fileList)
                }
                const urls = await uniCloud.getTempFileURL(fileList)
                return urls.fileList[0].tempFileURL || ''
            },
            /**
             * 获取父元素实例
             */
            getForm(name = 'uniForms') {
                let parent = this.$parent;
                let parentName = parent.$options.name;
                while (parentName !== name) {
                    parent = parent.$parent;
                    if (!parent) return false;
                    parentName = parent.$options.name;
                }
                return parent;
            }
        }
    }
</script>

<style>
    .uni-file-picker {
        /* #ifndef APP-NVUE */
        box-sizing: border-box;
        overflow: hidden;
        /* #endif */
    }

    .uni-file-picker__header {
        padding-top: 5px;
        padding-bottom: 10px;
        /* #ifndef APP-NVUE */
        display: flex;
        /* #endif */
        justify-content: space-between;
    }

    .file-title {
        font-size: 14px;
        color: #333;
    }

    .file-count {
        font-size: 14px;
        color: #999;
    }

    .is-add {
        /* #ifndef APP-NVUE */
        display: flex;
        /* #endif */
        align-items: center;
        justify-content: center;
    }

    .icon-add {
        width: 50px;
        height: 5px;
        background-color: #f1f1f1;
        border-radius: 2px;
    }

    .rotate {
        position: absolute;
        transform: rotate(90deg);
    }
</style>
